package ac.zlf.springmybatis.core.mybatis;

import ac.zlf.springmybatis.core.mybatis.domain.Page;
import ac.zlf.springmybatis.core.mybatis.domain.PageImpl;
import ac.zlf.springmybatis.core.mybatis.util.PropertiesHelper;
import ac.zlf.springmybatis.core.mybatis.dialect.Dialect;
import ac.zlf.springmybatis.core.mybatis.domain.PageRequest;
import ac.zlf.springmybatis.core.mybatis.util.SQLHelper;
import java.lang.reflect.Constructor;
import java.util.List;
import java.util.Properties;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.FutureTask;

import org.apache.commons.lang3.StringUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.ibatis.cache.Cache;
import org.apache.ibatis.cache.CacheKey;
import org.apache.ibatis.executor.Executor;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.mapping.MappedStatement.Builder;
import org.apache.ibatis.mapping.ParameterMapping;
import org.apache.ibatis.mapping.SqlSource;
import org.apache.ibatis.plugin.Interceptor;
import org.apache.ibatis.plugin.Intercepts;
import org.apache.ibatis.plugin.Invocation;
import org.apache.ibatis.plugin.Plugin;
import org.apache.ibatis.session.Configuration;
import org.apache.ibatis.session.RowBounds;

@Intercepts({@org.apache.ibatis.plugin.Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class, RowBounds.class, org.apache.ibatis.session.ResultHandler.class})})
public class PaginationInterceptor implements Interceptor {
    private static final Log logger = LogFactory.getLog(PaginationInterceptor.class);
    private static int MAPPED_STATEMENT_INDEX = 0;
    private static int PARAMETER_INDEX = 1;
    private static int ROWBOUNDS_INDEX = 2;
    private static int RESULT_HANDLER_INDEX = 3;
    String dialectClass;
    boolean asyncTotalCount;
    static ExecutorService Pool;

    public PaginationInterceptor() {
        this.asyncTotalCount = false;
    }

    public Object intercept(final Invocation invocation)
            throws Throwable {
        final Executor executor = (Executor) invocation.getTarget();
        Object[] queryArgs = invocation.getArgs();
        final MappedStatement ms = (MappedStatement) queryArgs[MAPPED_STATEMENT_INDEX];
        final Object parameter = queryArgs[PARAMETER_INDEX];
        RowBounds rowBounds = (RowBounds) queryArgs[ROWBOUNDS_INDEX];
        if ((rowBounds == null) || (rowBounds == RowBounds.DEFAULT)) {
            return invocation.proceed();
        }
        if (logger.isDebugEnabled()) {
            logger.debug("分页拦截开始 ==>>>>>>>");
        }
        PageRequest pageRequest = (PageRequest) rowBounds;
        final Dialect dialect;
        try {
            Class clazz = Class.forName(this.dialectClass);
            Constructor constructor = clazz.getConstructor(new Class[]{MappedStatement.class, Object.class, PageRequest.class});
            dialect = (Dialect) constructor.newInstance(new Object[]{ms, parameter, pageRequest});
        } catch (Exception e) {
            throw new ClassNotFoundException("Cannot create dialect instance: " + this.dialectClass, e);
        }
        final BoundSql boundSql = ms.getBoundSql(parameter);
        if (StringUtils.isEmpty(boundSql.getSql())) {
            return invocation.proceed();
        }
        queryArgs[MAPPED_STATEMENT_INDEX] = copyFromNewSql(ms, boundSql, dialect.getPageSQL(), dialect.getParameterMappings(), dialect.getParameterObject());
        queryArgs[PARAMETER_INDEX] = dialect.getParameterObject();
        queryArgs[ROWBOUNDS_INDEX] = new RowBounds(0, 2147483647);

        Boolean async = Boolean.valueOf(pageRequest.getAsyncTotalCount() == null ? this.asyncTotalCount : pageRequest.getAsyncTotalCount().booleanValue());
        Future<List> listFuture = call(new Callable() {
            public List call()
                    throws Exception {
                return (List) invocation.proceed();
            }
        }, async.booleanValue());
        if (pageRequest.isContainsTotalCount()) {
            Callable<Integer> countTask = new Callable() {
                public Object call()
                        throws Exception {
                    Cache cache = ms.getCache();
                    Integer count = null;
                    if ((cache != null) && (ms.isUseCache()) && (ms.getConfiguration().isCacheEnabled())) {
                        CacheKey cacheKey = executor.createCacheKey(ms, parameter, new PageRequest(), PaginationInterceptor.this.copyFromBoundSql(ms, boundSql, dialect.getCountSQL(), boundSql.getParameterMappings(), boundSql.getParameterObject()));
                        count = (Integer) cache.getObject(cacheKey);
                        if (count == null) {
                            count = Integer.valueOf(SQLHelper.getCount(ms, parameter, boundSql, dialect));
                            cache.putObject(cacheKey, count);
                        }
                    } else {
                        count = Integer.valueOf(SQLHelper.getCount(ms, parameter, boundSql, dialect));
                    }
                    return count;
                }
            };
            Future<Integer> countFuture = call(countTask, async.booleanValue());
            Page<?> page = new PageImpl((List) listFuture.get(), pageRequest, ((Integer) countFuture.get()).intValue());
            return page;
        }
        return listFuture.get();
    }

    private <T> Future<T> call(Callable callable, boolean async) {
        if (async) {
            return Pool.submit(callable);
        }
        FutureTask<T> future = new FutureTask(callable);
        future.run();
        return future;
    }

    private MappedStatement copyFromNewSql(MappedStatement ms, BoundSql boundSql, String sql, List<ParameterMapping> parameterMappings, Object parameter) {
        BoundSql newBoundSql = copyFromBoundSql(ms, boundSql, sql, parameterMappings, parameter);
        return copyFromMappedStatement(ms, new BoundSqlSqlSource(newBoundSql));
    }

    private BoundSql copyFromBoundSql(MappedStatement ms, BoundSql boundSql, String sql, List<ParameterMapping> parameterMappings, Object parameter) {
        BoundSql newBoundSql = new BoundSql(ms.getConfiguration(), sql, parameterMappings, parameter);
        for (ParameterMapping mapping : boundSql.getParameterMappings()) {
            String prop = mapping.getProperty();
            if (boundSql.hasAdditionalParameter(prop)) {
                newBoundSql.setAdditionalParameter(prop, boundSql.getAdditionalParameter(prop));
            }
        }
        return newBoundSql;
    }

    private MappedStatement copyFromMappedStatement(MappedStatement ms, SqlSource newSqlSource) {
        MappedStatement.Builder builder = new MappedStatement.Builder(ms.getConfiguration(), ms.getId(), newSqlSource, ms.getSqlCommandType());

        builder.resource(ms.getResource());
        builder.fetchSize(ms.getFetchSize());
        builder.statementType(ms.getStatementType());
        builder.keyGenerator(ms.getKeyGenerator());
        if (ms.getKeyProperties() != null) {
            for (String keyProperty : ms.getKeyProperties()) {
                builder.keyProperty(keyProperty);
            }
        }
        builder.timeout(ms.getTimeout());
        builder.parameterMap(ms.getParameterMap());
        builder.resultMaps(ms.getResultMaps());
        builder.cache(ms.getCache());
        return builder.build();
    }

    public Object plugin(Object target) {
        return Plugin.wrap(target, this);
    }

    public void setProperties(Properties properties) {
        PropertiesHelper propertiesHelper = new PropertiesHelper(properties);
        String dialectClass = propertiesHelper.getRequiredString("dialectClass");
        setDialectClass(dialectClass);

        setAsyncTotalCount(propertiesHelper.getBoolean("asyncTotalCount", false));

        setPoolMaxSize(propertiesHelper.getInt("poolMaxSize", 0));
    }

    public static class BoundSqlSqlSource
            implements SqlSource {
        BoundSql boundSql;

        public BoundSqlSqlSource(BoundSql boundSql) {
            this.boundSql = boundSql;
        }

        public BoundSql getBoundSql(Object parameterObject) {
            return this.boundSql;
        }
    }

    public void setDialectClass(String dialectClass) {
        logger.debug("dialectClass: {" + dialectClass + "}");
        this.dialectClass = dialectClass;
    }

    public void setAsyncTotalCount(boolean asyncTotalCount) {
        logger.debug("asyncTotalCount: { " + asyncTotalCount + "");
        this.asyncTotalCount = asyncTotalCount;
    }

    public void setPoolMaxSize(int poolMaxSize) {
        if (poolMaxSize > 0) {
            logger.debug("poolMaxSize: { " + poolMaxSize + "}");
            Pool = Executors.newFixedThreadPool(poolMaxSize);
        } else {
            Pool = Executors.newCachedThreadPool();
        }
    }
}



/* Location:           D:\repository\cc\hwtech\core\2.0.1-school-SNAPSHOT\core-2.0.1-school-SNAPSHOT.jar

 * Qualified Name:     PaginationInterceptor

 * JD-Core Version:    0.7.0.1

 */
